#!/usr/bin/env python
#encoding: utf-8

''' build script for 3rd packages
'''

import sys, os

class Package(object):
  # package attributes:
  # name, file_name, dbg_build, release_build,
  # test, install, prebuild, postbuild
  def __init__(self, name, file_name,
               **kws):
    self.name = name
    self.file_name = file_name
    self.__exatts = kws

  def __getattribute__(self, att):
    try:
      return object.__getattribute__(self, att)
    except:
      if att in self.__exatts:
        return self.__exatts[att]
      return ''


def defpack(*args, **kvargs):
  return Package(*args, **kvargs)

# environment variables
ENV = {
  'project_dir' : '',
  'unpack_dir' : '', # unpack target directory
  'package_path' : '', # package file full path
  'unpack_cmd': '', # unpackage command
  'build_dir' : '',
  'install_dir' : '',
  'job_num' : 14,
  'ccflags' : '',
  }

PACKAGES = (

  defpack('zlib', 'zlib-1.2.3.tar.gz',
          install='''\
make install
cd $install_dir/include
mkdir zlib
mv zconf.h zlib.h zlib
cd -

'''),

  defpack('swig', 'swig-2.0.4.tar.gz',
          build='''\
./configure --without-pcre --prefix=$install_dir --with-gflags=$install_dir
check_ret 'configure failed!!!'

make -j $job_num
''',
          install = '''
make install
''',
          ),

  # google packages
  defpack('gflags', 'gflags-2.0.tar.gz'),
  defpack('glog', 'glog-0.3.1.tar.gz',
          build='''\
./configure --prefix=$install_dir --with-gflags=$install_dir
check_ret 'configure failed!!!'

patch src/logging.cc $project_dir/packages/glog-0.3.1.patch
check_ret 'patch failed!!!'

make -j $job_num
''',
          ),
  defpack('gtest', 'gtest-1.5.0.tar.gz',
          build='''\
./configure --prefix=$install_dir
check_ret 'configure failed!!!'

make -j $job_num
''',
          ),

  defpack('protobuf', 'protobuf-2.5.0.tar.gz',
          build2='#',
          build='''\
$ccflags ./configure --prefix=$install_dir --enable-shared
check_ret 'configure failed!!!'

make -j $job_num

# build python module
cd python

cp $project_dir/packages/setuptools-0.6c11-py2.7.egg ./
export LD_LIBRARY_PATH=$install_dir/lib:$$LD_LIBRARY_PATH
PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=cpp /usr/local/bin/python2.7 setup.py build
check_ret 'compile python module failed!!!'
#PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=cpp /usr/local/bin/python2.7 setup.py install

cd -
''',
          install='''\
make install
rm -rf $install_dir/python_package/google
[ ! -d "$install_dir/python_package" ] && mkdir -p $install_dir/python_package

cp -r python/build/lib.linux-x86_64-2.7/google $install_dir/python_package
#cd $install_dir/include/google/protobuf/io
#mv -f coded_stream.h coded_stream.h.raw
#sed '/InternalReadStringInline/,+1s/^  /  \\/\\/ /' coded_stream.h.raw > coded_stream.h
#check_ret 'post process include/google/protobuf/io/coded_stream.h failed!!!'

#cd $install_dir/python_package/google
#patch __init__.py $project_dir/packages/protobuf_python_init.diff
#check_ret 'post process python_package/google/__init__.py failed!!!'

'''),
)
  

DBG_BUILD_ROOT = 'build/dbg'

DBG_INSTALL_ROOT = 'install/dbg'

OPT_with_dbg = True
OPT_with_test = False

def get_unpack_info_by_filename(file_name):
  ext_to_cmd = (
    ('.tar', 'tar -xf'),
    ('.tgz', 'tar -xzf'),
    ('.tar.gz', 'tar -xzf'),
    ('.tar.bz2', 'tar -xjf'),
    ('.gz', 'gunzip'),
    ('.zip', 'unzip'),
    )

  for ext, cmd in ext_to_cmd:
    if file_name[-len(ext):] == ext:
      return cmd, file_name[:-len(ext)]
  return None, None


def setup_build_env(pack, is_debug):
  unpack_cmd, subdir = get_unpack_info_by_filename(pack.file_name)
  if not unpack_cmd:
    raise Exception('can not unpack for %s, file name:%s' % (pack.name, pack.file_name))

  global ENV
  join_path = os.path.join
  ENV['unpack_dir'] = join_path(ENV['project_dir'],
                                is_debug and DBG_BUILD_ROOT or RELEASE_BUILD_ROOT)
  ENV['build_dir'] = join_path(ENV['unpack_dir'],
                               pack.unpack_dir and pack.unpack_dir or subdir)
  ENV['package_path'] = join_path(ENV['project_dir'], 'packages', pack.file_name)
  ENV['unpack_cmd'] = unpack_cmd
  ENV['install_dir'] = join_path(ENV['project_dir'],
                                 is_debug and DBG_INSTALL_ROOT or RELEASE_INSTALL_ROOT)


def render_string(val, pack = None):
  from string import Template
  t = Template(val)
  return t.substitute(ENV)


def build_package(pack, is_debug = True, with_test = False,
                  with_install = True):
  setup_build_env(pack, is_debug)
  get_a_or_b = lambda a, b: a and a or b
  R = render_string

  global ENV
  ENV['prebuild_cmd'] = R(pack.prebuild)
  build_cmd = R(is_debug and pack.dbg_build or pack.release_build)
  if build_cmd == '':
    build_cmd = R(get_a_or_b(
        pack.build,
        '$ccflags ./configure --prefix=$install_dir && make -j $job_num'))
  ENV['build_cmd'] = build_cmd
  ENV['postbuild_cmd'] = pack.postbuild

  test_cmd = R(get_a_or_b(pack.test, 'make test'))
  install_cmd = R(get_a_or_b(pack.install, 'make install'))

  if with_test:
    last_cmd = test_cmd
  elif with_install:
    last_cmd = install_cmd
  else:
    last_cmd = ''
  ENV['last_cmd'] = last_cmd

  build_script = '''\
check_ret() {
  ret=$$?
  if [ $$ret -ne 0 ]; then
    echo "$$1,retcode:$$ret"
    exit -1
  fi
}

# cd build path
cd $unpack_dir
check_ret "cd unpack dir failed!!!"

# extract package (tar -xzf /a/b/c.tgz)
$unpack_cmd $package_path
check_ret "unpack failed!!!"

# build
cd $build_dir

$prebuild_cmd
check_ret "prebuild failed!!!"

$build_cmd
check_ret "build failed!!!"

$postbuild_cmd
check_ret "postbuild failed!!!"

# test or install
$last_cmd
check_ret "install or test failed!!!"
'''
  real_build_script = R(build_script)
  script_file_name = 'build.cmd'
  script_file = open(script_file_name, 'w')
  script_file.write(real_build_script)
  script_file.close()
  ret = os.system('sh %s >> result.txt 2>&1' % script_file_name)
  if ret != 0:
    try:
      os.system('tail -n 50 result.txt')
    except:
      pass
    raise Exception('build %s failed' % pack.name)


def run_shell_cmd(cmd, ignore_error = True, err_msg = None):
  ret = os.system(cmd)
  if ret == 0:
    return

  if ignore_error:
    print ('Warning: exec "%s" failed' % cmd)
  else:
    if err_msg:
      raise Exception('Error: exec "%s" failed, %s' % (cmd, err_msg))
    else:
      raise Exception('Error: exec "%s" failed' % cmd)


def prepare_build():
  # prepare build env
  global ENV
  run_shell_cmd('mkdir -p %s' % DBG_BUILD_ROOT, False)
  run_shell_cmd('mkdir -p %s' % DBG_INSTALL_ROOT, False)
  cppflags = ' -g -D_DEBUG -fno-inline-functions -rdynamic -fPIC'
  ENV['ccflags'] = "CXXFLAGS='%s' CPPFLAGS='%s' CFLAGS=' -g -D_DEBUG -fPIC'" % (cppflags, cppflags)

  ENV['project_dir'] = os.path.abspath('.')
  try:
    import multiprocessing
    ENV['job_num'] = multiprocessing.cpu_count()
  except:
    ENV['job_num'] = 4

  os.system('rm -f result.txt')


def post_build():
  pass


def build(build_packages = None):
  print ('# Build %s version...' % (OPT_with_dbg and 'debug' or 'release'))
  print ('#' * 80)

  prepare_build()
  # build all packages
  succ_packs = []
  fail_packs = []
  for pack in PACKAGES:
    if not build_packages or pack.name in build_packages:
      try:
        print ('# Build %s...' % pack.name)
        build_package(pack, OPT_with_dbg, OPT_with_test)
        print ('# successfully')
        succ_packs.append(pack.name)
      except Exception, ex:
        print '# ' + (str(ex))
        fail_packs.append(pack.name)

  print ('# %d packages build successfully:%s' % (len(succ_packs), ' '.join(succ_packs)))
  print ('# %d packages build failure:%s' % (len(fail_packs), ' '.join(fail_packs)))

  post_build()


def main():
  argv = sys.argv[1:]
  import getopt
  opts, args = getopt.getopt(argv, 'hdlt')
  if ('-h', '') in opts:
    print ('usage: python build.py OPTS [pack1 pack2 ...]')
    print (' -d: debug version')
    print (' -t: make & test')
    print (' -h: print this help info')
    print (' -l: list all packages')
    print (' pack1 pack2 ...: package names, compile all packages if not supply')
    return

  if ('-l', '') in opts:
    print ' '.join([pack.name for pack in PACKAGES])
    return

  global OPT_with_dbg
  global OPT_with_test

  if ('-d', '') in opts:
    OPT_with_dbg = True
  if ('-t', '') in opts:
    OPT_with_test = True

  if len(args) == 0:
    args = None
  build(args)


if __name__ == '__main__':
  main()

